﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AbstraX.Bindings;
using AbstraX.Contracts;
using AbstraX.ServerInterfaces;
using AbstraX;
using System.Diagnostics;
using AbstraX.TypeMappings;

namespace CodeGenerationPipeline
{
    public class BindingsBuilder : IBindingsBuilder
    {
        private Dictionary<string, IElementBuild> elementBuilds;
        public IBindingsTreeNode BreakOnNode { set; private get; }

        public Dictionary<string, IElementBuild> GenerateBuilds(List<IBindingsTree> bindings)
        {
            elementBuilds = new Dictionary<string, IElementBuild>();

            AddBindings(bindings);
            ResolveNavigations();

            return elementBuilds;
        }

        private void CheckBreak(IBindingsTreeNode nodeParent, IElement childElement)
        {
            if (BreakOnNode != null)
            {
                var breakOnNode = (IBindingsTreeNode)BreakOnNode;
                var element = (IElement)null;

                if (breakOnNode.IsReference)
                {
                    element = ((IBindingsTreeNodeReference)breakOnNode).ReferencedFrom;
                }
                else if (BreakOnNode.DataContext != null)
                {
                    element = BreakOnNode.DataContext.First().ContextObject;
                }
                else
                {
                    return;
                }

                if (element.DataType.FullyQualifiedName != childElement.DataType.FullyQualifiedName)
                {
                    return;
                }
                else if (breakOnNode.Name != childElement.Name)
                {
                    return;
                }
                else if (breakOnNode.ParentNode != null && nodeParent != null)
                {
                    if (breakOnNode.ParentNode.Name != nodeParent.Name)
                    {
                        return;
                    }
                }
                else if (breakOnNode.ParentNode == null && nodeParent != null)
                {
                    return;
                }
                else if (breakOnNode.ParentNode != null && nodeParent == null)
                {
                    return;
                }

                Debugger.Break();

                IElement owner;
                object property;
                BaseType propertyType;

                breakOnNode.GetNavigationPropertyOwner(childElement, out owner, out property, out propertyType);
            }
        }

        protected void AddBindings(List<IBindingsTree> bindings)
        {
            foreach (var bindingsTree in bindings)
            {
                AddBindings(bindingsTree);
            }
        }

        protected void AddBindings(IBindingsTree bindingsTree)
        {
            foreach (var bindingsTreeNode in bindingsTree.RootBindings)
            {
                AddBindings(bindingsTree, bindingsTreeNode, true);
            }
        }

        protected void AddBindings(IBindingsTree bindingsTree, IBindingsTreeNode bindingNode, bool isRoot)
        {
            if (bindingNode.DataContext != null)
            {
                // for debugging

                var element = bindingNode.DataContext.First().ContextObject;
            }

            var rootAdded = false;

            if (!isRoot)
            {
                rootAdded = true;
            }

            if (bindingNode.PropertyBindings != null)
            {
                foreach (var propertyBinding in bindingNode.PropertyBindings)
                {
                    var element = GetElementForBinding(bindingNode, propertyBinding);
                    IElementBuild elementBuild;

                    if (elementBuilds.ContainsKey(element.DataType.FullyQualifiedName))
                    {
                        elementBuild = elementBuilds[element.DataType.FullyQualifiedName];

                        if (!elementBuild.Bindings.Any(p => p.PropertyBindingName == propertyBinding.PropertyBindingName))
                        {
                            elementBuild.Bindings.Add(propertyBinding);
                        }
                    }
                    else
                    {
                        elementBuild = new ElementBuild();

                        elementBuild.Element = element;
                        elementBuild.IsRootObject = isRoot;
                        elementBuild.Bindings.Add(propertyBinding);
                        elementBuild.GetDerivedTypes += new GetDerivedTypesHandler((out List<IElementBuild> list) =>
                        {
                            list = BindingsBuilder.GetDerivedTypes(elementBuild, elementBuilds.Values);
                        });

                        rootAdded = true;

                        elementBuilds.Add(element.DataType.FullyQualifiedName, elementBuild);
                    }
                }
            }

            if (bindingNode.ChildNodes != null)
            {
                foreach (var childBinding in bindingNode.ChildNodes)
                {
                    foreach (var childElement in childBinding.GetContextObjects())
                    {
                        object navigationProperty;
                        IElement navigationOwner;
                        BaseType propertyType;

                        CheckBreak(bindingNode, childElement);

                        childBinding.GetNavigationPropertyOwner(childElement, out navigationOwner, out navigationProperty, out propertyType);

                        if (navigationOwner != null)
                        {
                            IElementBuild elementBuild;

                            if (elementBuilds.ContainsKey(navigationOwner.DataType.FullyQualifiedName))
                            {
                                elementBuild = elementBuilds[navigationOwner.DataType.FullyQualifiedName];

                                if (!elementBuild.NavigationItems.Any(n => n.PropertyName == (navigationProperty is string ? (string)navigationProperty : ((IElement)navigationProperty).Name)))
                                {
                                    if (navigationProperty is string)
                                    {
                                        var navigationItem = new NavigationItem((string)navigationProperty, childElement, propertyType);

                                        elementBuild.NavigationItems.Add(navigationItem);
                                    }
                                    else if (navigationProperty is IElement)
                                    {
                                        var navigationItem = new NavigationItem((IElement)navigationProperty, childElement, propertyType);

                                        elementBuild.NavigationItems.Add(navigationItem);
                                    }
                                    else
                                    {
                                        Debugger.Break();
                                    }
                                }
                            }
                            else
                            {
                                elementBuild = new ElementBuild();

                                elementBuild.Element = navigationOwner;
                                elementBuild.GetDerivedTypes += new GetDerivedTypesHandler((out List<IElementBuild> list) =>
                                {
                                    list = BindingsBuilder.GetDerivedTypes(elementBuild, elementBuilds.Values);
                                });

                                if (!rootAdded)
                                {
                                    elementBuild.IsRootObject = isRoot;
                                    rootAdded = true;
                                }

                                if (navigationProperty is string)
                                {
                                    var navigationItem = new NavigationItem((string)navigationProperty, childElement, propertyType);

                                    elementBuild.NavigationItems.Add(navigationItem);
                                }
                                else if (navigationProperty is IElement)
                                {
                                    var navigationItem = new NavigationItem((IElement)navigationProperty, childElement, propertyType);

                                    elementBuild.NavigationItems.Add(navigationItem);
                                }
                                else
                                {
                                    Debugger.Break();
                                }

                                elementBuilds.Add(navigationOwner.DataType.FullyQualifiedName, elementBuild);
                            }
                        }

                        AddBindings(bindingsTree, childBinding, rootAdded == false);
                    }
                }
            }

            if (bindingsTree.GenerateStatelessConstructs)
            {
                if (bindingNode.DataContext != null)
                {
                    foreach (var element in bindingNode.DataContext.Select(d => d.ContextObject))
                    {
                        IElementBuild elementBuild;

                        if (!elementBuilds.ContainsKey(element.DataType.FullyQualifiedName))
                        {
                            elementBuild = new ElementBuild();

                            elementBuild.Element = element;
                            elementBuild.GetDerivedTypes += new GetDerivedTypesHandler((out List<IElementBuild> list) =>
                            {
                                list = BindingsBuilder.GetDerivedTypes(elementBuild, elementBuilds.Values);
                            });

                            if (!rootAdded)
                            {
                                elementBuild.IsRootObject = isRoot;
                                rootAdded = true;
                            }

                            elementBuilds.Add(element.DataType.FullyQualifiedName, elementBuild);
                        }
                    }
                }
            }
        }

        private static List<IElementBuild> GetDerivedTypes(IElementBuild elementBuild, IEnumerable<IElementBuild> fromBuilds)
        {
            var derivedTypes = new List<IElementBuild>();

            foreach (IElementBuild build in fromBuilds)
            {
                if (build.Element.DataType.InheritsFrom(elementBuild.Element.DataType))
                {
                    derivedTypes.Add(build);
                }

                if (elementBuild.Element.DataType.IsInterface)
                {
                    // here

                    if (build.Element.DataType.Implements(elementBuild.Element.DataType))
                    {
                        derivedTypes.Add(build);
                    }
                }
            }

            return derivedTypes;
        }

        protected void ResolveNavigations()
        {
            foreach (var elementBuild in elementBuilds.Values)
            {
                var remove = new List<NavigationItem>();

                foreach (var navigationItem in elementBuild.NavigationItems)
                {
                    if (elementBuilds.ContainsKey(navigationItem.NavigationResultElement.DataType.FullyQualifiedName))
                    {
                        var resultElementBuild = elementBuilds[navigationItem.NavigationResultElement.DataType.FullyQualifiedName];

                        navigationItem.NavigationResultBuild = resultElementBuild;
                    }
                    else
                    {
                        remove.Add(navigationItem);
                    }
                }

                remove.ForEach(n => elementBuild.NavigationItems.Remove(n));
            }
        }

        private IElement GetElementForBinding(IBindingsTreeNode bindingNode, IPropertyBinding binding)
        {
            var context = bindingNode.DataContext.FirstOrDefault(e => binding.BindingSource is IAbstraXBindingSource ?
                e.ContextObject.ID == ((IAbstraXBindingSource)binding.BindingSource).BindingAttribute.ParentID
                : false /* TODO */);

            if (context != null)
            {
                return context.ContextObject;
            }
            else
            {
                return GetElementForBinding(bindingNode.ParentNode, binding);
            }
        }
    }
}
